package net.betou.cms.action.admin.assist;

import static net.betou.core.manager.AuthenticationMng.AUTH_KEY;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.betou.cms.Constants;
import net.betou.cms.entity.back.CmsField;
import net.betou.cms.manager.assist.CmsDataBackMng;
import net.betou.cms.manager.assist.CmsResourceMng;
import net.betou.cms.manager.main.CmsLogMng;
import net.betou.cms.web.WebErrors;
import net.betou.common.util.DateUtils;
import net.betou.common.util.StrUtils;
import net.betou.common.util.Zipper;
import net.betou.common.util.Zipper.FileEntry;
import net.betou.common.web.RequestUtils;
import net.betou.common.web.ResponseUtils;
import net.betou.common.web.session.SessionProvider;
import net.betou.common.web.springmvc.RealPathResolver;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * 
 * @author 忘记一切、重新开始
 * 
 */
@Controller
public class DataAct{
	private static String SUFFIX = "sql";
	private static String SPLIT = "`";
	private static String BR = "\r\n";
	private static String SLASH = "/";
	private static String SPACE = " ";
	private static String BRANCH = ";";
	private static String INSERT_INTO = " INSERT INTO ";
	private static String VALUES = "VALUES";
	private static String LEFTBRACE = "(";
	private static String RIGHTBRACE = ")";
	private static String QUOTES = "'";
	private static String COMMA = ",";
	private static String DISABLEFOREIGN = "SET FOREIGN_KEY_CHECKS = 0;\r\n";
	private static String ABLEFOREIGN = "SET FOREIGN_KEY_CHECKS = 1;\r\n";
	private static String dbXmlFileName = "/WEB-INF/config/jdbc.properties";
	private static final String INVALID_PARAM = "template.invalidParams";
	private static String backup_table;
	private static final Logger log = LoggerFactory.getLogger( ResourceAct.class );

	@RequestMapping( "/data/v_list.do" )
	public String list( ModelMap model, HttpServletRequest request, HttpServletResponse response ) {
		List< String > tables = new ArrayList< String >( );
		try{
			tables = dataBackMng.listTabels( dataBackMng.getDefaultCatalog( ) );
		}catch( SQLException e ){
			model.addAttribute( "msg", e.toString( ) );
			return "common/error_message";
		}
		model.addAttribute( "tables", tables );
		return "data/list";
	}

	@RequestMapping( "/data/v_listfields.do" )
	public String listfiled( String tablename, ModelMap model, HttpServletRequest request, HttpServletResponse response ) {
		List< CmsField > list = dataBackMng.listFields( tablename );
		model.addAttribute( "list", list );
		return "data/fields";
	}

	@RequestMapping( "/data/v_revert.do" )
	public String listDataBases( ModelMap model, HttpServletRequest request, HttpServletResponse response ) {
		try{
			String defaultCatalog = dataBackMng.getDefaultCatalog( );
			model.addAttribute( "defaultCatalog", defaultCatalog );
		}catch( SQLException e ){
			model.addAttribute( "msg", e.toString( ) );
			return "common/error_message";
		}
		List< String > databases = dataBackMng.listDataBases( );
		model.addAttribute( "databases", databases );
		model.addAttribute( "backuppath", Constants.BACKUP_PATH );
		return "data/databases";
	}

	@RequestMapping( "/data/o_revert.do" )
	public String revert( String filename, String db, ModelMap model, HttpServletRequest request, HttpServletResponse response ) throws IOException {
		String backpath = realPathResolver.get( Constants.BACKUP_PATH );
		String backFilePath = backpath + SLASH + filename;
		String sql = readFile( backFilePath );
		// 还原暂时没做备份提示。
		dataBackMng.executeSQL( "use " + SPLIT + db + SPLIT + BR );
		dataBackMng.executeSQL( sql );
		// 若db发生变化，需要处理jdbc
		try{
			String defaultCatalog = dataBackMng.getDefaultCatalog( );
			if( !defaultCatalog.equals( db ) ){
				String dbXmlPath = realPathResolver.get( dbXmlFileName );
				dbXml( dbXmlPath, defaultCatalog, db );
			}
		}catch( Exception e ){
			WebErrors errors = WebErrors.create( request );
			errors.addErrorCode( "db.revert.error" );
			errors.addErrorString( e.getMessage( ) );
			if( errors.hasErrors( ) ){
				return errors.showErrorPage( model );
			}
		}
		// 需要重启web服务器，可能会存在bug
		session.setAttribute( request, response, AUTH_KEY, null );
		request.getSession( ).invalidate( );
		return "login";
	}

	@RequestMapping( "/data/o_backup.do" )
	public String backup( String tableNames[], ModelMap model, HttpServletRequest request, HttpServletResponse response ) throws IOException, InterruptedException {
		String backpath = realPathResolver.get( Constants.BACKUP_PATH );
		File backDirectory = new File( backpath );
		if( !backDirectory.exists( ) ){
			backDirectory.mkdir( );
		}
		DateUtils dateUtils = DateUtils.getDateInstance( );
		String backFilePath = backpath + SLASH + dateUtils.getNowString( ) + "." + SUFFIX;
		File file = new File( backFilePath );
		Thread thread = new DateBackupTableThread( file, tableNames );
		thread.start( );
		return "data/backupProgress";
	}

	@RequestMapping( "/data/v_listfiles.do" )
	public String listBackUpFiles( ModelMap model, HttpServletRequest request, HttpServletResponse response ) {
		model.addAttribute( "list", resourceMng.listFile( Constants.BACKUP_PATH, false ) );
		return "data/files";
	}

	@RequestMapping( "/data/v_selectfile.do" )
	public String selectBackUpFiles( ModelMap model, HttpServletRequest request, HttpServletResponse response ) {
		model.addAttribute( "list", resourceMng.listFile( Constants.BACKUP_PATH, false ) );
		return "data/selectfile";
	}

	@RequestMapping( "/data/o_delete.do" )
	public String delete( String root, String [ ] names, HttpServletRequest request, ModelMap model, HttpServletResponse response ) {
		WebErrors errors = validateDelete( names, request );
		if( errors.hasErrors( ) ){
			return errors.showErrorPage( model );
		}
		int count = resourceMng.delete( names );
		log.info( "delete Resource count: {}", count );
		for ( String name : names ){
			log.info( "delete Resource name={}", name );
			cmsLogMng.operating( request, "resource.log.delete", "filename=" + name );
		}
		model.addAttribute( "root", root );
		return listBackUpFiles( model, request, response );
	}

	@RequestMapping( "/data/o_delete_single.do" )
	public String deleteSingle( HttpServletRequest request, ModelMap model, HttpServletResponse response ) {
		// TODO 输入验证
		String name = RequestUtils.getQueryParam( request, "name" );
		int count = resourceMng.delete( new String [ ] { name } );
		log.info( "delete Resource {}, count {}", name, count );
		cmsLogMng.operating( request, "resource.log.delete", "filename=" + name );
		return listBackUpFiles( model, request, response );
	}

	@RequestMapping( value = "/data/v_rename.do" )
	public String renameInput( HttpServletRequest request, ModelMap model ) {
		String name = RequestUtils.getQueryParam( request, "name" );
		String origName = name.substring( Constants.BACKUP_PATH.length( ) );
		model.addAttribute( "origName", origName );
		return "data/rename";
	}

	@RequestMapping( value = "/data/o_rename.do", method = RequestMethod.POST )
	public String renameSubmit( String root, String origName, String distName, HttpServletRequest request, ModelMap model, HttpServletResponse response ) {
		String orig = Constants.BACKUP_PATH + origName;
		String dist = Constants.BACKUP_PATH + distName;
		resourceMng.rename( orig, dist );
		log.info( "name Resource from {} to {}", orig, dist );
		model.addAttribute( "root", root );
		return listBackUpFiles( model, request, response );
	}

	@RequestMapping( value = "/data/o_export.do" )
	public String exportSubmit( String [ ] names, ModelMap model, HttpServletRequest request, HttpServletResponse response ) throws UnsupportedEncodingException {
		if( validate( names, request ) ){
			WebErrors errors = WebErrors.create( request );
			errors.addErrorCode( INVALID_PARAM );
			return errors.showErrorPage( model );
		}
		String backName = "back";
		if( names[ 0 ] != null ){
			backName = names[ 0 ].substring( names[ 0 ].indexOf( Constants.BACKUP_PATH ) + Constants.BACKUP_PATH.length( ) + 1 );
		}
		List< FileEntry > fileEntrys = new ArrayList< FileEntry >( );
		response.setContentType( "application/x-download;charset=UTF-8" );
		response.addHeader( "Content-disposition", "filename=" + backName + ".zip" );
		for ( String filename : names ){
			File file = new File( realPathResolver.get( filename ) );
			fileEntrys.add( new FileEntry( "", "", file ) );
		}
		try{
			// 模板一般都在windows下编辑，所以默认编码为GBK
			Zipper.zip( response.getOutputStream( ), fileEntrys, "GBK" );
		}catch( IOException e ){
			log.error( "export db error!", e );
		}
		return null;
	}

	@RequestMapping( "/data/o_backup_progress.do" )
	public void getBackupProgress( HttpServletRequest request, HttpServletResponse response ) throws JSONException {
		JSONObject json = new JSONObject( );
		json.put( "tablename", backup_table );
		ResponseUtils.renderJson( response, json.toString( ) );
	}

	public void dbXml( String fileName, String oldDbHost, String dbHost ) throws Exception {
		String s = FileUtils.readFileToString( new File( fileName ) );
		s = StringUtils.replace( s, oldDbHost, dbHost );
		FileUtils.writeStringToFile( new File( fileName ), s );
	}

	private String readFile( String filename ) throws IOException {
		File file = new File( filename );
		if( filename == null || filename.equals( "" ) ){
			throw new NullPointerException( "<@s.m 'db.fileerror'/>" );
		}
		long len = file.length( );
		byte [ ] bytes = new byte [ ( int ) len ];
		BufferedInputStream bufferedInputStream = new BufferedInputStream( new FileInputStream( file ) );
		int r = bufferedInputStream.read( bytes );
		if( r != len )
			throw new IOException( "<@s.m 'db.filereaderror'/>" );
		bufferedInputStream.close( );
		return new String( bytes, "utf-8" );
	}

	private WebErrors validateDelete( String [ ] names, HttpServletRequest request ) {
		WebErrors errors = WebErrors.create( request );
		errors.ifEmpty( names, "names" );
		for ( String id : names ){
			vldExist( id, errors );
		}
		return errors;
	}

	private boolean vldExist( String name, WebErrors errors ) {
		if( errors.ifNull( name, "name" ) ){
			return true;
		}
		return false;
	}

	private boolean validate( String [ ] names, HttpServletRequest request ) {
		if( names != null ){
			for ( String name : names ){
				// 导出阻止非法获取其他目录文件
				if( !name.contains( "/WEB-INF/backup/" ) ){
					return true;
				}
			}
		}
		return false;
	}

	private class DateBackupTableThread extends Thread{
		private File file;
		private String [ ] tablenames;

		public DateBackupTableThread( File file, String [ ] tablenames ) {
			super( );
			this.file = file;
			this.tablenames = tablenames;
		}

		public void run( ) {
			FileOutputStream out;
			OutputStreamWriter writer = null;
			try{
				out = new FileOutputStream( file );
				writer = new OutputStreamWriter( out, "utf8" );
				writer.write( Constants.ONESQL_PREFIX + DISABLEFOREIGN );
				for ( int i = 0; i < tablenames.length; i++ ){
					backup_table = tablenames[ i ];
					backupTable( writer, tablenames[ i ] );
				}
				writer.write( Constants.ONESQL_PREFIX + ABLEFOREIGN );
				backup_table = "";
				writer.close( );
				out.close( );
			}catch( IOException e ){
				e.printStackTrace( );
			}
		}

		private String backupTable( OutputStreamWriter writer, String tablename ) throws IOException {
			writer.write( createOneTableSql( tablename ) );
			writer.flush( );
			return tablename;
		}

		private String createOneTableSql( String tablename ) {
			StringBuffer buffer = new StringBuffer( );
			Object [ ] oneResult;
			buffer.append( Constants.ONESQL_PREFIX + "DROP TABLE IF EXISTS " + tablename + BRANCH + BR );
			buffer.append( Constants.ONESQL_PREFIX + dataBackMng.createTableDDL( tablename ) + BRANCH + BR + Constants.ONESQL_PREFIX );
			List< Object [ ] > results = dataBackMng.createTableData( tablename );
			for ( int i = 0; i < results.size( ); i++ ){
				// one insert sql
				oneResult = results.get( i );
				buffer.append( createOneInsertSql( oneResult, tablename ) );
			}
			return buffer.toString( );
		}

		private String createOneInsertSql( Object [ ] oneResult, String tablename ) {
			StringBuffer buffer = new StringBuffer( );
			buffer.append( Constants.ONESQL_PREFIX + INSERT_INTO + SPLIT + tablename + SPLIT + SPACE + VALUES + LEFTBRACE );
			for ( int j = 0; j < oneResult.length; j++ ){
				if( oneResult[ j ] != null ){
					if( oneResult[ j ] instanceof Date ){
						buffer.append( QUOTES + oneResult[ j ] + QUOTES );
					}else if( oneResult[ j ] instanceof String ){
						buffer.append( QUOTES + StrUtils.replaceKeyString( ( String ) oneResult[ j ] ) + QUOTES );
					}else if( oneResult[ j ] instanceof Boolean ){
						if( ( Boolean ) oneResult[ j ] ){
							buffer.append( 1 );
						}else{
							buffer.append( 0 );
						}
					}else{
						buffer.append( oneResult[ j ] );
					}
				}else{
					buffer.append( oneResult[ j ] );
				}
				buffer.append( COMMA );
			}
			buffer = buffer.deleteCharAt( buffer.lastIndexOf( COMMA ) );
			buffer.append( RIGHTBRACE + BRANCH + BR );
			return buffer.toString( );
		}
	}

	@Autowired
	private RealPathResolver realPathResolver;
	@Autowired
	private CmsDataBackMng dataBackMng;
	@Autowired
	private CmsResourceMng resourceMng;
	@Autowired
	private CmsLogMng cmsLogMng;
	@Autowired
	private SessionProvider session;
}
